package run;

import influence.Influence;
import influence.InfluenceAggregator;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import action.Action;
import action.ActionGenerator;
import action.DirectionMutatingAction;

import paint.SandPainter;
import processing.core.PApplet;
import processing.core.PVector;
import event.Event;
import event.EventObserver;
import event.collide.CollideRule;
import event.collide.InflectionDirectionRule;
import event.collide.RandomPastPerpendicularSpawnRule;
import event.dist.DistRule;
import event.dist.RepulseRule;

public class LineCell {
	
	public int generation;
	public PVector loc;
	public PVector dir;
	public PApplet p;
	public boolean dead = false;
	public SandPainter paint;
	public List<PVector> pastLocs;
	List<DirectionMutatingAction> dirChangers = new LinkedList<DirectionMutatingAction>();
	List<DistRule> distRules = new LinkedList<DistRule>();
	
	public int lifetime;
	
	private List<CollideRule> collideRules = new LinkedList<CollideRule>();

	public int r,g,b;
	private EventObserver eventObserver;
	private InfluenceAggregator influenceAggregator;
	private ActionGenerator actionGenerator;
	
	private HashMap<Action, Float> desire_queue;
	private float maxWidth = 3f;
	
	public LineCell(PVector init_pos, PVector init_direction, int gen) {
		loc = init_pos;
		dir = init_direction;
		dir.normalize();
		generation = gen;
		
		p = Doodler.instance;
		paint = new SandPainter(p);
		pastLocs = new LinkedList<PVector>();
		
		desire_queue = new HashMap<Action, Float>();
		
		
		eventObserver = new EventObserver(this);
		influenceAggregator = new InfluenceAggregator();
		actionGenerator = new ActionGenerator();
		
		//REPLACE with rule handler; will choose for us.
		distRules.add(new RepulseRule());
		
		r = (int) p.random(100,250);
		g = (int) p.random(40,150);
		b = (int) p.random(20,200);
		
		lifetime = (int) p.random(200,1000);
	}
	
	public int getAge() {
		return pastLocs.size();
	}
	
	// normalizes a value to a new domain
	private float castToRange(float min, float max, float omin, float omax, float value) {
		if (value > omax) value = omax;
		if (value < omin) value = omin;
		return ( value / (omax - omin) ) * (max - min);
	}
	
	public void move() {
		Env e = Env.instance;
		if (dead) return;
	    recordLocation(e);
	    
	    // paint(params)
	    linearStrokeDensity(loc, pastLocs.get(0));
	    
	    List<Event> currEvents = eventObserver.poll();
	    
	    // each event will spur a 'desire'; the desires must be aggregated.
	    HashMap<Action, Float> new_desires = influenceAggregator.sum(currEvents);
	    
	    HashMap<Action, Float> all_desires = new HashMap<Action, Float>();
	    all_desires.putAll(desire_queue);
	    desire_queue.clear();
	    all_desires.putAll(new_desires);
	    exec(all_desires);
	    
	    
	    //TODO take care of distance-related rules... factor out...
	    PVector distSum = new PVector(0,0,0);
	    for (LineCell c : e.cells) {
		    for (DistRule d : distRules) {
		    	distSum.add(d.execRule(this, c, e, p));
		    }
	    }
	    distSum.normalize();
	    distSum.mult(0.01f);
	    
	    // PAST PARAMS UPDATING
	    //   update Dir, Loc
	    dir.add(distSum);
	    dir.normalize();
	    loc.add(dir);
	}
	
	private void exec(HashMap<Action, Float> desires) {
		Set<Action> actions = desires.keySet();
		for (Action a : actions) {
			if (a.isActive()) {
				a.doAction(this, desires.get(a));
				desire_queue.put(a, desires.get(a));
			}
		};
	}

	private void hazyLine() {
		// draw the hazy line
	    /*
	    float hw = 3;
	    for (int i = 0; i < 5; i++) {
	      float hazyX = x+random(-hw, hw);
	      float hazyY = y+random(-hw, hw);
	      PVector hazy = new PVector(hazyX, hazyY, 0);
	      // dist can be 0 to .43*5=2.5... mult by 30
	      float strokeAlph = 10 + 30 / (loc.dist(hazy) * loc.dist(hazy));
	      p.stroke(r,g,b, strokeAlph);
	      p.point(hazyX, hazyY);
	    }
	    */
	    
	}

	// Stroke Density is a complicated function of various inputs....
	// TODO should write it out
	// we want strokedensity a function of pos, pos-original, 
	//     controlling parameters of alpha and spread
	private void linearStrokeDensity(PVector curPos, PVector origPos) {
	    
	    float x = curPos.x;
	    float y = curPos.y;
	    
	    // TODO have some of these be random variables...
	    //TODO some start weak and end strong, others start strong and end weak...
	    // perhaps tie it to their proximity to other lines!!!
	    
	    maxWidth = 4f; 
	    float max_dens = 30;
	    float min_dens = 5;
	    float reduct_rate = 0.5F;
	    float dens = larger(max_dens - reduct_rate * (curPos.dist(origPos)), min_dens);
	    
	    int numNearbyCells = Env.instance.getNearbyCells(this, 20).size();
		dens += (numNearbyCells + 2) * 5F ;

	    float width = castToRange(1F, maxWidth, min_dens, max_dens, dens);
	    width -= (numNearbyCells + 1);
	    if (width < 0.5) width = 0.5F;
	    
	    int point_count = (int) (width * 2);
	    
	    //TODO another set of (+) terms accounting for nearby cell distances
	    point_count = (int) ((numNearbyCells + 2) * 5F) / (generation + 1) ;

	    for (int i = 0; i < point_count; i++) {

	    	// ALSO depend upon generation(a bit) & dist from nearest neighbors...
	    	// alpha smaller the greater the width
	    	p.stroke(r,g,b, 10 + 70 / (width + 0.35F*generation*generation + 3*numNearbyCells ));
		    p.point(x+random(-width,width),y+random(-width,width));
	    }
	}
	
	private float larger(float x, float y) {
		if (x > y)
			return x;
		else return y;
	}

	private void recordLocation(Env e) {
		pastLocs.add(new PVector(loc.x, loc.y, 0));
	    e.addHist(this, loc);
	}

	private float random(float x, float y) {
		return p.random(x,y);
	}

	public void addDirChanger(DirectionMutatingAction d) {
		dirChangers.add(d);
	}

	public void notifyEvent(Event e) {
		eventObserver.queueExternalEvent(e);
	}

	public void boostStroke(float i) {
		maxWidth += i;
		if (maxWidth > 10) maxWidth = 10;
		if (maxWidth < 3) maxWidth = 3;
			
	}
	
	
}
